package com.gitee.wsl.text.reader

import com.gitee.wsl.ext.string.indexOfNextToken
import com.gitee.wsl.ext.string.indexOfNextUnescapedQuote
import com.gitee.wsl.ext.string.findNextNewLineChar
import com.gitee.wsl.ext.string.findBeforeNewLineChar
import com.gitee.wsl.ext.base.ifValue
import com.gitee.wsl.text.predicate.CharMatcher
import kotlin.math.min
import kotlin.math.pow

class MicroStrReader(val str: CharSequence, val baseIndex:Int = 0) {
    val length get() = str.length
    val available get() = str.length - offset
    val hasMore get() = offset < str.length
    val eof get() = !hasMore

    var offset: Int = baseIndex

    val readLength get() = offset - baseIndex

    //var peekOffset:Int = baseIndex

    inline fun readChunk(callback: () -> Unit): String {
        val start = this.offset
        callback()
        val end = this.offset
        return this.str.substring(start, end)
    }

    fun step(count: Int = 1) {
        offset += count
        require( offset in 0..length)
    }

    fun goto(newIndex:Int){
        require( newIndex in 0..length)
        offset = newIndex
    }

    fun peekChar(): Char = str[offset]

    private fun peekCharOrZero(): Char = if (hasMore) str[offset] else '\u0000'

    val peek get() = peekChar()

    //val peekString get() = str.substring(peekOffset,offset)

    val peekOrZero get() = peekCharOrZero()

    val prevChar get() = (offset > 0).ifValue(str[offset-1],null)

    val nextChar get() = (offset < length).ifValue(str[offset+1],null)

    fun readChar(): Char = str[offset++]

    fun tryRead(expected: Char): Boolean {
        if (eof || peekChar() != expected) return false
        readChar()
        return true
    }

    fun tryReadOrNull(expected: String): String? {
        return if (tryRead(expected)) expected else null
    }

    fun tryRead(expected: String): Boolean {
        if (expected.length > available) return false
        for (n in expected.indices) if (this.str[offset + n] != expected[n]) return false
        offset += expected.length
        return true
    }

    fun tryRead(block:(Char)->Boolean):String{
        val peekOffset = offset
        while (hasMore && block(peek))
            step()
        return str.substring(peekOffset,offset)
    }

    fun tryReadString(block:(CharSequence)->Boolean):String{
        val peekOffset = offset
        var nextOffset = min(offset+1,length)
        var matcher = false
        while ((nextOffset < length) && block(str.substring(offset,nextOffset))) {
            nextOffset++
            matcher = true
        }
        if(matcher) step(nextOffset-peekOffset)
        return str.substring(peekOffset,nextOffset)
    }

    fun tryRead(block: CharMatcher) = tryRead{ block.test(it) }

    fun takeWhile(block:(Char)->Boolean) = tryRead {!block(it)}

    fun read(count: Int): String = this.str.substring(offset, (offset + count).coerceAtMost(length)).also { this.offset += it.length }

    fun readRemaining(): String = read(available)

    fun readInt(count: Int): Int = read(count).toInt()

    fun tryReadInt(count: Int): Int? = read(count).toIntOrNull()

    fun tryReadDouble(count: Int): Double? = read(count).replace(',', '.').toDoubleOrNull()

    fun tryReadDouble(): Double? {
        var numCount = 0
        var num = 0
        var denCount = 0
        var den = 0
        var decimals = false
        loop@while (hasMore) {
            when (val pc = peekChar()) {
                ',' -> {
                    if (numCount == 0) {
                        return null
                    }
                    decimals = true
                    readChar()
                }
                in '0'..'9' -> {
                    val c = readChar()
                    if (decimals) {
                        denCount++
                        den *= 10
                        den += (c - '0')
                    } else {
                        numCount++
                        num *= 10
                        num += (c - '0')
                    }
                }
                else -> {
                    break@loop
                }
            }
        }
        if (numCount == 0) {
            return null
        }
        return num.toDouble() + (den.toDouble() * 10.0.pow(-denCount))
    }
}

fun String.useReader(offset:Int=0, block: MicroStrReader.()->Unit) = block(MicroStrReader(this,offset))

fun MicroStrReader.indexOfNextToken() = str.indexOfNextToken(offset)

fun MicroStrReader.indexOfNextUnescapedQuote() = str.indexOfNextUnescapedQuote(offset)

fun MicroStrReader.findNextNewLineChar() = str.findNextNewLineChar(offset)

fun MicroStrReader.findBeforeNewLineChar() = str.findBeforeNewLineChar(offset)


